package com.example.day23_h264parse2;

/**
 * Description:解析H264的sps信息
 * Created by Gdu on 2020/12/18 15:21.
 */

public class PaserH264 {

    static int nStartBit = 0;

    public static void main(String[] args) {
        //通过哥伦布编码解析出sps中的信息
        String str = "00 00 00 01 67 64 00 15 AC D9 41 70 C6 84 00 00 03 00 04 00 00 03 00 F0 3C 58 B6 58";
        parseH264Sps(str);
    }

    //region 解析sps
    public static void parseH264Sps(String str){
        byte[] h264 = hexStringToByteArray(str);
        nStartBit = 4 * 8;//跳过开头的 00 00 00 01
        //forbidden_zero_bit：禁止位，初始为0，当网络发现NAL单元有比特错误时可设置该比特为1，以便接收方纠错或丢掉该单元。
        int forbidden_zero_bit = getByte(1, h264);
        //nal_ref_idc：nal重要性指示，标志该NAL单元的重要性，值越大，越重要，解码器在解码处理不过来的时候，可以丢掉重要性为0的NALU。
        int nal_ref_idc = getByte(2, h264);
        //nal_unit_type：NALU：帧类型
        int nal_unit_type = getByte(5, h264);
        //帧类型为7代表是sps  为8是pps
        if (nal_unit_type == 7) {
            //profile_idc： 编码等级
            int profile_idc = getByte(8, h264);
            //当constrained_set0_flag值为1的时候，就说明码流应该遵循基线profile(Baseline profile)的所有约束.constrained_set0_flag值为0时，说明码流不一定要遵循基线profile的所有约束。
            int constraint_set0_flag = getByte(1, h264);//(h264[1] & 0x80)>>7;
            // 当constrained_set1_flag值为1的时候，就说明码流应该遵循主profile(Main profile)的所有约束.constrained_set1_flag值为0时，说明码流不一定要遵
            int constraint_set1_flag = getByte(1, h264);//(h264[1] & 0x40)>>6;
            //当constrained_set2_flag值为1的时候，就说明码流应该遵循扩展profile(Extended profile)的所有约束.constrained_set2_flag值为0时，说明码流不一定要遵循扩展profile的所有约束。
            int constraint_set2_flag = getByte(1, h264);//(h264[1] & 0x20)>>5;
            //注意：当constraint_set0_flag,constraint_set1_flag或constraint_set2_flag中不只一个值为1的话，那么码流必须满足所有相应指明的profile约束。
            int constraint_set3_flag = getByte(1, h264);//(h264[1] & 0x10)>>4;
            //4个零位
            int reserved_zero_4bits = getByte(4, h264);
            //它指的是码流对应的level级
            int level_idc = getByte(8, h264);
            //seq_parameter_set_id表示当前的序列参数集的id。通过该id
            int seq_parameter_set_id = parseCode(h264);
            //编码等级为100说明视频是 High (FRExt)
            if (profile_idc == 100) {
                // chroma_format_idc  与亮度取样对应的色度取样 .  值应该在 0到 3的范围内（包括 0和 3）  yuv420  yuv422 yuv 444
                //颜色位深   8  10  0
                int chroma_format_idc = parseCode(h264);
                //bit_depth_luma_minus8   视频位深   0 八位   1 代表10位
                int bit_depth_luma_minus8 = parseCode(h264);
                int bit_depth_chroma_minus8 = parseCode(h264);
                //qpprime_y_zero_transform_bypass_flag    占用1个bit,当前使用到的字符为0xAC，  y轴 0标志位
                int qpprime_y_zero_transform_bypass_flag = getByte(1, h264);
                //缩放换标志位
                int seq_scaling_matrix_present_flag = getByte(1, h264);
            }
            //最大帧率
            int log2_max_frame_num_minus4 = parseCode(h264);
            //确定播放顺序和解码顺序的映射
            int pic_order_cnt_type = parseCode(h264);

            int log2_max_pic_order_cnt_lsb_minus4 = parseCode(h264);
            //编码索引  码流顺序

            int num_ref_frames = parseCode(h264);
            int gaps_in_frame_num_value_allowed_flag = getByte(1, h264);
            System.out.println("------startBit " + nStartBit);
            int pic_width_in_mbs_minus1 = parseCode(h264);
            System.out.println("------startBit " + nStartBit);
            int pic_height_in_map_units_minus1 = parseCode(h264);
            System.out.println("======");// 宏快数
            //计算宽高须把宏块数+1
            int width = (pic_width_in_mbs_minus1 + 1) * 16;
            int height = (pic_height_in_map_units_minus1 + 1) * 16;
            System.out.println("width :  " + width + "   height: " + height);
        }
    }
    //endregion

    //region 使用哥伦布编码解析byte数组
    public static int parseCode(byte[] pBuff) {
        int nZeroNum = 0;
        //统计0的个数
        while (nStartBit < pBuff.length * 8) {
            if ((pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8))) != 0) {
                break;
            }
            nZeroNum++;
            nStartBit++;
        }
        nStartBit++;
        int dwRet = 0;
        for (int i = 0; i < nZeroNum; i++) {
            dwRet <<= 1;
            if ((pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8))) != 0) {
                dwRet += 1;
            }
            nStartBit++;
        }
        int value = (1 << nZeroNum) + dwRet - 1;
        System.out.println("value is " + value);
        return value;
    }
    //endregion

    //region 把十六进制的字符串转化成字符数组
    public static byte[] hexStringToByteArray(String s) {
        s=s.replace(" ", "");//去掉空格
        //十六进制转byte数组
        int len = s.length();
        byte[] bs = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            bs[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
        }
        return bs;
    }
    //endregion

    //region 取出字节数组中的bitIndex个字节
    private static int getByte(int bitIndex, byte[] h264) {
//    位 索引  字节索引   bitIndex 32
//            3           bitIndex位数
//        0   100 0000
//        nStartBit 开始 100换成10进制
        int dwRet = 0;
        for (int i = 0; i < bitIndex; i++) {
            dwRet <<= 1;
            if ((h264[nStartBit / 8] & (0x80 >> (nStartBit % 8))) != 0) {
                dwRet += 1;
            }
            nStartBit++;
        }
        return dwRet;
    }
    //endregion

}
